package com.itheima.d8_thread_pool;

import java.util.concurrent.*;

/**
 * 目标：掌握线程池的创建。
 *什么是线程池？
 *      线程池就是一个可以复用线程的技术。
 * 不使用线程池的问题:
 *     用户每发起一个请求，后台就需要创建一个新线程来处理，下次新任务来了肯定又要创建新线程处理的，
 *     而创建新线程的开销是很大的，并且请求过多时，肯定会产生大量的线程出来，这样会严重影响系统的性能。
 *线程池的工作原理
 *
 * 谁代表线程池？
 * JDK 5.0起提供了代表线程池的接口：ExecutorService。
 * 如何得到线程池对象?
 * 方式一：使用ExecutorService接口的实现类ThreadPoolExecutor自创建一个线程池对象。
 * 方式二：使用Executors（线程池的工具类）调用方法返回不同特点的线程池对象。
 *
 * ThreadPoolExecutor构造器：
 *public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
 *      TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
 *      RejectedExecutionHandler handler)
 *
 * 参数一：corePoolSize : 指定线程池的核心线程的数量。
 * 参数二：maximumPoolSize：指定线程池的最大线程数量。
 * 参数三：keepAliveTime ：指定临时线程的存活时间。
 * 参数四：unit：指定临时线程存活的时间单位(秒、分、时、天）
 * 参数五：workQueue：指定线程池的任务队列。    里面的任务其实是对象，这个任务对象必须是实现了Runnable或者Callable接口
 * 参数六：threadFactory：指定线程池的线程工厂。 是负责为线程池创建线程的
 * 参数七：handler：指定线程池的任务拒绝策略（线程都在忙，任务队列也满了的时候，新任务来了该怎么处理）
 *
 *线程池的注意事项：
 * 1、临时线程什么时候创建？
 * 新任务提交时发现核心线程都在忙，任务队列也满了，并且还可以创建临时线程，此时才会创建临时线程。
 * 2、什么时候会开始拒绝新任务？
 * 核心线程和临时线程都在忙，任务队列也满了，新的任务过来的时候才会开始拒绝任务。
 *
 * ExecutorService的常用方法
 *          方法名称
 * void execute(Runnable command)           执行 Runnable 任务
 * Future<T> submit(Callable<T> task)       执行 Callable 任务，返回未来任务对象，用于获取线程返回的结果
 * void shutdown()                          等全部任务执行完毕后，再关闭线程池！
 * List<Runnable>shutdownNow()              立刻关闭线程池，停止正在执行的任务，并返回队列中未执行的任务
 *
 * 新任务拒绝策略
 *          策略                                          详解
 *  ThreadPoolExecutor.AbortPolicy              丢弃任务并抛出RejectedExecutionException异常。是默认的策略
 *  ThreadPoolExecutor.DiscardPolicy：          丢弃任务，但是不抛出异常 这是不推荐的做法
 *  ThreadPoolExecutor.DiscardOldestPolicy      抛弃队列中等待最久的任务 然后把当前任务加入队列中
 *  ThreadPoolExecutor.CallerRunsPolicy         由主线程负责调用任务的run()方法从而绕过线程池直接执行
 *
 *  Executors:
 *          是一个线程池的工具类，提供了很多静态方法用于返回不同特点的线程池对象。
 *
 *              方法名称
 *  public static ExecutorService newFixedThreadPool (int nThreads)
 *  创建固定线程数量的线程池，如果某个线程因为执行异常而结束，那么线程池会补充一个新线程替代它。
 *
 *  public static ExecutorService newSingleThreadExecutor()
 *  创建只有一个线程的线程池对象，如果该线程出现异常而结束，那么线程池会补充一个新线程。
 *
 *  public static ExecutorService newCachedThreadPool()
 *  线程数量随着任务增加而增加，如果线程任务执行完毕且空闲了60s则会被回收掉。
 *
 *  public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize)
 *  创建一个线程池，可以实现在给定的延迟后运行任务，或者定期执行任务。
 *
 *  注意 ：这些方法的底层，都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
 *
 *  Executors使用可能存在的陷阱
 * 大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
 *
 *
 * 阿里巴巴 Java 开发手册
 * 【强制】线程池不允许使用 Executors去创建，而是通过 ThreadPoolExecutor的方式，
 * 这样的处理方式让写的同学更加明确线程池的运行规则，规避资源耗尽的风险。
 * 说明:Executors 返回的线程池对象的弊端如下:
 *      1)FixedThreadPool和SingleThreadPool:
 *          允许的请求队列长度为 Integer.MAx_VALUE，可能会堆积大量的请求，从而导致 00M。
 * 磊哥：Executors的创建固定和单个线程的线程池对象，这两个所允许的请求队列长度是最大的，也就是说不会控制任务的数量可以无限的
 *      往线程池里面堆积这些任务对象，所以可能会导致大量的请求堆积，导致内存溢出异常
 *      2)CachedThreadPool和ScheduledThreadPool.
 *          允许的创建线程数量为 Integer.MAX_VALUE，可能会创建大量的线程，从而导致 00M。
 *磊哥：允许创建的线程数量为最大值，也就是说如果有一亿个用户过来就会创建一亿个线程，也会导致内存溢出
 *       总结：
 *  Executors工具类底层是基于什么方式实现的线程池对象？
 * 线程池ExecutorService的实现类：ThreadPoolExecutor
 * Executors是否适合做大型互联网场景的线程池方案？
 * 不合适。
 * 建议使用ThreadPoolExecutor来指定线程池参数，这样可以明确线程池的运行规则，规避资源耗尽的风险。
 */
public class ThreadPoolTest1 {
    public static void main(String[] args) {
        /*  ExecutorService接口的实现类ThreadPoolExecutor自创建一个线程池对象，七大参数
            public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

           TimeUnit是一个枚举类，源码：指定线程存活时间的单位，public enum TimeUnit
           任务队列：ArrayBlockingQueue<>()，这个是基于数组实现的
                   LinkedBlockingDeque<>()，这个是基于链表实现的，是不限制大小的，可以无限的往这个队列里加东西

           ThreadFactory线程工厂是一个接口，是不能直接new对象的，可以用匿名内部类的形式，但是会显得参数列表非常麻烦
           所以能够用Executors工具类调用defaultThreadFactory方法：Executors.defaultThreadFactory()，这个是获取一个默认的线程池工厂

           public static class CallerRunsPolicy implements RejectedExecutionHandler
           new ThreadPoolExecutor.CallerRunsPolicy()的CallerRunsPolicy()这是一个ThreadPoolExecutor的内部类，
           这个内部类的对象也是RejectedExecutionHandler接口的一个实现类对象


 * 新任务拒绝策略
 *          策略                                          详解
 *  ThreadPoolExecutor.AbortPolicy              丢弃任务并抛出RejectedExecutionException异常。是默认的策略
 *  ThreadPoolExecutor.DiscardPolicy：          丢弃任务，但是不抛出异常 这是不推荐的做法
 *  ThreadPoolExecutor.DiscardOldestPolicy      抛弃队列中等待最久的任务 然后把当前任务加入队列中
 *  ThreadPoolExecutor.CallerRunsPolicy         由主线程负责调用任务的run()方法从而绕过线程池直接执行
         */
        // 1、通过ThreadPoolExecutor创建一个线程池对象。把得到的线程池对象交给ExecutorService，多态写法
        // * 方式一：使用ExecutorService接口的实现类ThreadPoolExecutor自创建一个线程池对象。
        //多态写法，用ExecutorService来接
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()); //CallerRunsPolicy 由主线程负责执行新线程的run方法

        //创建Runnable类型的任务对象
        Runnable target = new MyRunnable();
        // ExecutorService的常用方法: void execute(Runnable command)  执行 Runnable 任务
        //创建的线程池对象核心线程数量是3，所以前三个会很快的创建核心线程来处理任务
        pool.execute(target); // 线程池会自动创建一个新线程，自动处理这个任务，自动执行的！
        pool.execute(target); // 线程池会自动创建一个新线程，自动处理这个任务，自动执行的！
        pool.execute(target); // 线程池会自动创建一个新线程，自动处理这个任务，自动执行的！

        //在任务对象MyRunnable那里写Thread.sleep(Integer.MAX_VALUE);然后满足任务队列上限的四个
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //任务队列已满，到了临时线程的创建时机了
        pool.execute(target);
        pool.execute(target);

        //任务队列满了，设置的两个临时线程数量也满了。再创建新的线程时，就会到了新任务的拒绝时机了！也就是拒绝策略
        /*
 当前面创建线程池时的新任务拒绝策略为：ThreadPoolExecutor.AbortPolicy  丢弃任务并抛出RejectedExecutionException异常。是默认的策略
 也就是new ThreadPoolExecutor.AbortPolicy() 的时候，会抛出异常：
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.itheima.d8_thread_pool.MyRunnable@378bf509 rejected from java.util.concurrent.ThreadPoolExecutor@5fd0d5ae[Running, pool size = 5, active threads = 5, queued tasks = 4, completed tasks = 0]
         */
        pool.execute(target);

        // 等着线程池的任务全部执行完毕后，再关闭线程池
        // pool.shutdown();

        // 立即关闭线程池！不管任务是否执行完毕！
        // pool.shutdownNow();
    }
}
